/* 
 * Cashbox is the legal property of its developers, whose names are listed in the copyright file included
 * with this source distribution.
 * 
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
 * General Public License as published by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
 * Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program; if not,
 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#import "CBQIFImporter.h"
#import <Cashbox/CBAttribute.h>
#import <ObjCRegex/ObjCRegex.h>

#define ACCOUNT_NAME			@"name"

#define TRANSACTION_LABEL		@"label"
#define TRANSACTION_DATE		@"date"
#define TRANSACTION_CLEARED		@"cleared"

#define TRANSACTION_DATE_DAY	@"day"
#define TRANSACTION_DATE_MONTH	@"month"
#define TRANSACTION_DATE_YEAR	@"year"

@interface CBQIFTransaction : NSObject {
	int day, month, year;
	BOOL cleared, containsInfo;
	NSString *label;
	id <NSObject> reference;
	NSMutableDictionary *attributes;
}
+ (id)transaction;
- (int)dateDay;
- (void)setDateDay:(int)day;
- (int)dateMonth;
- (void)setDateMonth:(int)month;
- (int)dateYear;
- (void)setDateYear:(int)year;
- (BOOL)cleared;
- (void)setCleared:(BOOL)cleared;
- (NSString *)label;
- (void)setLabel:(NSString *)label;
- (id <NSObject>)reference;
- (void)setReference:(id <NSObject>)reference;
- (NSDictionary *)attributes;
- (void)setAttribute:(id)object forRole:(CBRoleType)role;
- (BOOL)containsInfo;
@end

@implementation CBQIFImporter

+ (void)load {
	[CBImporter registerImporter:self];
}

+ (NSArray *)canHandleExtensions {
	return [NSArray arrayWithObjects:@"qif", nil];
}

- (id)init {
	if (self = [super init]) {
		transactions = [[NSMutableArray alloc] init];
	}
	return self;
}

- (void)dealloc {
	[transactions release];
	[super dealloc];
}

- (void)addDataFromFile:(NSString *)file {
	
	PCRegex *amountRegex = [PCRegex regexWithPattern:@"^T(-)?([\\d,]+).(\\d\\d)"];
	PCRegex *descriptionRegex = [PCRegex regexWithPattern:@"^P(.*)"];
	PCRegex *labelRegex = [PCRegex regexWithPattern:@"^L(.*)"];
	PCRegex *dateRegex = [PCRegex regexWithPattern:@"^D(\\d\\d?).(\\d\\d?).(\\d\\d?\\d?\\d?)"];
	PCRegex *clearedRegex = [PCRegex regexWithPattern:@"^C"];
	
	PCRegex *newAccountRegex = [PCRegex regexWithPattern:@"!Account"];
	PCRegex *accountNameRegex = [PCRegex regexWithPattern:@"^N(.*)"];
	
	PCRegex *startAutoSwitch = [PCRegex regexWithPattern:@"!Option:AutoSwitch"];
	PCRegex *endAutoSwitch = [PCRegex regexWithPattern:@"!Clear:AutoSwitch"];
	
	PCRegex *endItemRegex = [PCRegex regexWithPattern:@"^\\^"];
	
	CBQIFTransaction *transactionInProgress = nil;
	NSMutableDictionary *accountInProgress = nil;
	NSObject *currentAccountReference = nil;
	BOOL autoSwitchInProgress = FALSE;
	BOOL noMoreAutoSwitch = FALSE;
	
	// get the contents of the file
	NSString *contents = [NSString stringWithContentsOfFile:file];
	NSArray *lines = [contents allLines];
	
	NSArray *backreferences = nil;
	NSString *line;
	NSEnumerator *lineEnumerator = [lines objectEnumerator];
	while (line = [lineEnumerator nextObject]) {

		if (!noMoreAutoSwitch && [line match:startAutoSwitch]) {
			autoSwitchInProgress = TRUE;
		} else if (autoSwitchInProgress) {
			if ([line match:endAutoSwitch]) {
				autoSwitchInProgress = FALSE;
				noMoreAutoSwitch = TRUE;
			}
		} else if ([line match:newAccountRegex]) {
			accountInProgress = [NSMutableDictionary dictionary];
			currentAccountReference = nil;
		} else if (accountInProgress) {
			if ([line match:accountNameRegex backreferences:&backreferences]) {
				NSString *name = [[backreferences objectAtIndex:1] string];
				if (name) { [accountInProgress setObject:name forKey:ACCOUNT_NAME]; }
			} else if ([line match:endItemRegex]) {
				// add the account
				[super addAccountWithName:[accountInProgress objectForKey:ACCOUNT_NAME]
						   additionalInfo:@""
									 keys:[[self class] defaultKeys]
								reference:&currentAccountReference];
				accountInProgress = nil;
			}
		} else {
			// create a new transaction if one doesn't already exist
			transactionInProgress = 
				(transactionInProgress ? transactionInProgress : [CBQIFTransaction transaction]);
			
			if ([line match:amountRegex backreferences:&backreferences]) {
				
				NSString *numberString = [[backreferences objectAtIndex:2] string];
				if (numberString) {
					NSString *result = @"";
					NSScanner *scanner = [NSScanner scannerWithString:numberString];
					NSCharacterSet *skipCharacters = 
						[NSCharacterSet characterSetWithCharactersInString:
						 [NSApp isOnLeopardOrBetter] ?
						 [[NSUserDefaults standardUserDefaults] objectForKey:NSThousandsSeparator] :
						 [[NSLocale currentLocale] objectForKey:NSLocaleGroupingSeparator]];
					
					[scanner setCharactersToBeSkipped:skipCharacters];
					while (![scanner isAtEnd]) {
						NSString *outString = nil;
						[scanner scanUpToCharactersFromSet:skipCharacters intoString:&outString];
						if (outString) { result = [result stringByAppendingString:outString]; }
					}
					
					long long amount = [result longLongValue] * 100;
					amount += [[[backreferences objectAtIndex:3] string] intValue];
					NSNumber *number = [NSNumber numberWithLongLong:amount];
					
					if ([[[backreferences objectAtIndex:1] string] isEqualToString:@"-"]) {
						[transactionInProgress setAttribute:number forRole:CBNegativeCashflowRole]; // withdrawal
					} else {
						[transactionInProgress setAttribute:number forRole:CBPositiveCashflowRole]; // deposit
					}
				}

			} else if ([line match:descriptionRegex backreferences:&backreferences]) {
				NSString *description = [[backreferences objectAtIndex:1] string];
				if (description) {
					[transactionInProgress setAttribute:description forRole:CBDescriptionRole];
				}
			} else if ([line match:labelRegex backreferences:&backreferences]) {
				NSString *label = [[backreferences objectAtIndex:1] string];
				if (label) {
					[transactionInProgress setLabel:label];
					[super noteLabel:label withColor:nil];
				}
			} else if ([line match:dateRegex backreferences:&backreferences]) {
				NSString *yearString, *monthString, *dayString;
				int year, month, day;
				yearString = [[backreferences objectAtIndex:3] string];
				monthString = [[backreferences objectAtIndex:1] string];
				dayString = [[backreferences objectAtIndex:2] string];
				
				if (yearString && monthString && dayString) {
					year = [yearString intValue];
					month = [monthString intValue];
					day = [dayString intValue];
					
					// check for switching (QIF = bleh)
					if (month > 12) { [self setDayMonthSwitched:YES]; }

					[transactionInProgress setDateYear:year];
					[transactionInProgress setDateMonth:month];
					[transactionInProgress setDateDay:day];					
				}
			} else if ([line match:clearedRegex]) {
				[transactionInProgress setCleared:TRUE];
			} else if ([line match:endItemRegex]) {
				// add the transaction
				if ([transactionInProgress containsInfo]) {
					id ref;
					[super addTransactionToAccount:currentAccountReference
									withAttributes:[transactionInProgress attributes]
											  date:nil
										reconciled:NO
										   cleared:[transactionInProgress cleared]
										 labelName:[transactionInProgress label]
										 reference:&ref];
					[transactionInProgress setReference:ref];
					[transactions addObject:transactionInProgress];
				}
				transactionInProgress = nil;
			}
		}
	}
}

- (void)importDataWillBegin {
	CBQIFTransaction *transaction;
	NSEnumerator *transactionEnumerator = [transactions objectEnumerator];
	while (transaction = [transactionEnumerator nextObject]) {
		int year = [transaction dateYear];
		int month = [transaction dateMonth];
		int day = [transaction dateDay];
		if ([self dayMonthSwitched]) {
			int hold = month;
			month = day;
			day = hold;
		}		
		[super updateTransactionDate:[NSCalendarDate dateWithYear:year
															month:month
															  day:day
															 hour:0 minute:0 second:0 timeZone:nil]
						   reference:[transaction reference]];
	}
}

+ (NSArray *)defaultKeys {
	return [NSArray arrayWithObjects:
		[NSNumber numberWithShort:CBDescriptionRole],
		[NSNumber numberWithShort:CBPositiveCashflowRole],
		[NSNumber numberWithShort:CBNegativeCashflowRole], nil];
}

- (BOOL)dayMonthSwitched {
	return dayMonthSwitched;
}
- (void)setDayMonthSwitched:(BOOL)flag {
	[self willChangeValueForKey:@"dayMonthSwitched"];
	dayMonthSwitched = flag;
	[self didChangeValueForKey:@"dayMonthSwitched"];
}

@end


@implementation CBQIFTransaction

+ (id)transaction {
	return [[[self alloc] init] autorelease];
}

- (id)init {
	if (self = [super init]) {
		attributes = [[NSMutableDictionary alloc] init];
	}
	return self;
}

- (void)dealloc {
	[label release];
	[reference release];
	[attributes release];
	
	[super dealloc];
}

- (int)dateDay { return day; }
- (void)setDateDay:(int)aDay {
	day = aDay;
	containsInfo = TRUE;
}

- (int)dateMonth { return month; }
- (void)setDateMonth:(int)aMonth {
	month = aMonth;
	containsInfo = TRUE;
}

- (int)dateYear { return year; }
- (void)setDateYear:(int)aYear {
	year = aYear;
	containsInfo = TRUE;
}

- (BOOL)cleared { return cleared; }
- (void)setCleared:(BOOL)clearedFlag {
	cleared = clearedFlag;
	containsInfo = TRUE;
}

- (NSString *)label {
	return label;
}
- (void)setLabel:(NSString *)aLabel {
	if (label != aLabel) {
		[label release];
		label = [aLabel retain];
		containsInfo = TRUE;
	}
}

- (id <NSObject>)reference { return reference; }
- (void)setReference:(id <NSObject>)aReference {
	if (reference != aReference) {
		[reference release];
		reference = [aReference retain];
		containsInfo = TRUE;
	}
}

- (NSDictionary *)attributes { return attributes; }
- (void)setAttribute:(id)object forRole:(CBRoleType)role {
	[attributes setObject:object forKey:[NSNumber numberWithShort:role]];
	containsInfo = TRUE;
}

- (BOOL)containsInfo { return containsInfo; }

@end
